/*
 * kernel/sched.c
 *
 * last modified by ChenJie at 2007.07.15
 *
 */
#include <asm/i8259.h>
#include <asm/i8253.h>
#include <cos/kernel.h>
#include <cos/sched.h>
#include <cos/mm.h>
#include <cos/time.h>
#include <cos/interrupt.h>
#include <cos/protect.h>
#include <cos/ipc.h>

void restore_context(struct context_struct *p);

struct procs_struct procs;


static void set_boot_procs(void)
{
	int i;
	struct proc_struct *p=0;
	for(i=0; i<NR_BOOT_PROCS; i++){
		p = (struct proc_struct *)mm_alloc_page();
		if(p==NULL) panic("can't alloc free page");

		// have got the free page
		
		p->counter = bootprocs[i].counter;
		p->status = TASK_RUNNING;
		p->priority = bootprocs[i].priority;
		p->task_nr = i;
		p->ktime = 0;
		p->utime = 0;
		p->next = 0;
		p->pid = bootprocs[i].pid;
		p->reenter = 0;
		p->start_time = procs.jiffies;
		p->ulevel = bootprocs[i].ulevel;
		
		/* ipc */
		p->waiting_for_sending = 0;
		p->waiting_for_receiving = 0;
		p->waiting_for_any_arrive = 0;
		msgbox_init(&p->msgbox);
		
		procs.task[p->task_nr] = p;
		if(p->pid==HARDWARE) continue;


		set_seg_desc(&(p->ldt.kstack),	// kernel_SS
			DA_DRW|DA_32|DA_LIMIT_1B|DA_DPL0,
			(unsigned long)p, 4096);
		set_seg_desc(&(p->ldt.ucode),	//USER_CS
			DA_CR  |DA_32|DA_LIMIT_4K|DA_DPL(p->ulevel), 
			TASK_TEXT_START, (TASK_TEXT_LIMIT)>>12);
		set_seg_desc(&(p->ldt.udata),		//USER_DS
			DA_DRW|DA_32|DA_LIMIT_4K|DA_DPL(p->ulevel),
			TASK_DATA_START, (TASK_DATA_LIMIT)>>12);
		set_seg_desc(&(p->ldt.ustack),	//USER_SS
			DA_DRW|DA_32|DA_LIMIT_4K|DA_DPL(p->ulevel),
			TASK_STACK_START, (TASK_STACK_LIMIT)>>12);

		p->tss.back_link = 0;
		p->tss.esp0 = (unsigned long)p+4096;
		p->tss.ss0 = SELECTOR_KDS;
		p->tss.esp1 = 0;
		p->tss.ss1 = 0;
		p->tss.esp2 = 0;
		p->tss.ss2 = 0;
		p->tss.cr3 = (unsigned long)create_pg_dir_from(pg_dir);
		p->tss.eip = (unsigned long)(bootprocs[i].entry);
		p->tss.eflags = 0x00000200;// set IF = 1, enable interrupts
		p->tss.eax = 0;
		p->tss.ecx = 0;
		p->tss.edx = 0;
		p->tss.ebx = 0;
		p->tss.esp = TASK_STACK_LIMIT;// esp ---> 1G
		p->tss.ebp = 0;
		p->tss.esi = 0;
		p->tss.edi = 0;
		p->tss.es  = SELECTOR_UDS(p->ulevel);
		p->tss.cs  = SELECTOR_UCS(p->ulevel);
		p->tss.ss  = SELECTOR_USS(p->ulevel);
		p->tss.ds  = SELECTOR_UDS(p->ulevel);
		p->tss.fs  = SELECTOR_UDS(p->ulevel);
		p->tss.gs  = SELECTOR_KGS;
		p->tss.ldt = TASK_LDT_SELECTOR(p->task_nr);
		p->tss.trace_bitmap = 0;
		//init_task.task.tss.i387 = {0,0...};

		set_tss_desc(&gdt[TASK_TSS_ENTRY(p->task_nr)], &p->tss);
		set_ldt_desc(&gdt[TASK_LDT_ENTRY(p->task_nr)], &p->ldt);

		// the following needs to be checked that if the task has the access privilege:
		copy_kernel_pages_to_user((unsigned long *)(p->tss.cr3), 0, 0xA0000, TASK_TEXT_START);
		copy_kernel_pages_to_user((unsigned long *)(p->tss.cr3), 0, 0xA0000, TASK_DATA_START);

		// a page should be alloced as the pagetable for the stack : use the paging
		// copy_kernel_pages_to_user(p->tss.cr3, 0, 0xA0000, TASK_STACK_START);

		enqueue(p);
	}
}

// only use by the main(), start the procs schedule:
void begin_schedule(void)
{
	int i;
	struct proc_struct *p = 0;
	struct context_struct *ucontextp;
	for(i=0; i<NR_PRIORITY; i++){
		// check if current priority-list has ready task:
		if(procs.ready_head[i]!=0) break;
	}
	p = procs.ready_head[i];

	// So process p is going to running......
	procs.running_proc= p;
	lldt(TASK_LDT_SELECTOR(p->task_nr));
	ltr(TASK_TSS_SELECTOR(p->task_nr));
	set_cr3(p->tss.cr3);

	// the running environment is ready, then configuring the register contexts:
	ucontextp = &(((struct kernel_stack_struct *)p)->ucontext);
	ucontextp->gs = p->tss.gs;
	ucontextp->fs = p->tss.fs;
	ucontextp->es = p->tss.es;
	ucontextp->ds = p->tss.ds;
	ucontextp->ebp = p->tss.ebp;
	ucontextp->esi = p->tss.esi;
	ucontextp->edi = p->tss.edi;
	ucontextp->edx = p->tss.edx;
	ucontextp->ecx = p->tss.ecx;
	ucontextp->ebx = p->tss.ebx;
	ucontextp->eax = p->tss.eax;
	ucontextp->eip = p->tss.eip;
	ucontextp->cs = p->tss.cs;
	ucontextp->eflags = p->tss.eflags;
	ucontextp->esp = p->tss.esp;
	ucontextp->ss = p->tss.ss;

	// restore the register contexts to run the process......
	restore_context(ucontextp);
	
	// never return here!
}


// add a proc to the ready-queue:
long enqueue(struct proc_struct * proc_ptr)
{
	//check if the proc_ptr's priority is validate:
	if(proc_ptr->priority < 0 || proc_ptr->priority >= NR_PRIORITY) 
		return FAILURE;//fail
	
	if(procs.ready_tail[proc_ptr->priority]==NULL){
		// current queue-list is empty, so make the head and tail point to the proc:
		procs.ready_head[proc_ptr->priority] = proc_ptr;
		procs.ready_tail[proc_ptr->priority] = proc_ptr;
		proc_ptr->next = NULL;
	}else{
		// add the new proc to the tail of the queue-list:
		procs.ready_tail[proc_ptr->priority]->next = proc_ptr;
		proc_ptr->next = NULL;
		procs.ready_tail[proc_ptr->priority] = proc_ptr;
	}
	return SUCCESS; //successful
}

// remove a proc from the ready-queue:
long dequeue(struct proc_struct * proc_ptr)
{
	struct proc_struct *p;
	//check if the proc_ptr's priority is validate:
	if(proc_ptr->priority < 0 || proc_ptr->priority >= NR_PRIORITY) 
		return FAILURE;//fail
		
	// find the the pointer of proc which is  before proc_ptr:
	p = procs.ready_head[proc_ptr->priority];
	
	if(p==NULL)	return FAILURE;//success
	
	if(p==proc_ptr){
		procs.ready_head[proc_ptr->priority] = proc_ptr->next;
		if(procs.ready_tail[proc_ptr->priority]==proc_ptr) {
			procs.ready_tail[proc_ptr->priority] = NULL;
		}
		return SUCCESS; //successful
	}
	//the first proc of the list is not the proc_ptr, then ...
	while(p->next!=NULL && p->next != proc_ptr) p=p->next;

	// check if the proc_ptr has found:
	if(p->next == NULL) return FAILURE; //fail
	
	// remove the proc_ptr from the queue-list:
	p->next = proc_ptr->next;
	proc_ptr->next = NULL;

	// check if the proc_ptr is the tail of the queue:
	if(procs.ready_tail[proc_ptr->priority]==proc_ptr) 
		procs.ready_tail[proc_ptr->priority] = p;

	return SUCCESS; //success
}

void check_schedule(void)
{
	if(procs.running_proc->status != TASK_RUNNING 
		|| procs.running_proc->counter == 0){
		schedule();
	}
}

void schedule(void)
{
	int i;
	for(i=0; i<NR_PRIORITY; i++){
		// check if current priority-list has ready task:
		if(procs.ready_head[i]!=NULL) break;
	}
	proc_switch_to(procs.ready_head[i]);
}


struct proc_struct * get_proc_ptr(long pid)
{
	int i;
	for(i=0; i<NR_TASKS; i++){
		if((procs.task[i]!=NULL) && ( procs.task[i]->pid == pid)) 
			return procs.task[i];
	}
	return NULL;
}
long is_sleeping_on(long pid, struct proc_struct ** event)
{
	struct proc_struct * p = *event;
	
	if(pid == ANY_PROC) {
		;	
	}else {
		while( (p!=NULL) && (p->pid!= pid))  p = p->next;
	}
	if(p==NULL) return FALSE;
	return TRUE; //success
}
void sleep_on(struct proc_struct ** event)
{
	dequeue(procs.running_proc);
	// change the status of the proc:
	procs.running_proc->status = TASK_INTERRUPTIBLE;
	// add to the waiting list:
	procs.running_proc->next = *event;
	*event = procs.running_proc;

	// give up the cpu:
	schedule();
}

long wake_up(long pid, struct proc_struct ** event)
{
	struct proc_struct * proc_ptr;
	struct proc_struct ** p;
	
	if(*event == NULL) return FAILURE;
	
	if(pid == ANY_PROC) {
		// remove the first waiting proc of the waiting list:
		proc_ptr = *event;
		*event = proc_ptr->next;
		proc_ptr->next = NULL;
		//
		proc_ptr->status = TASK_RUNNING;
		enqueue(proc_ptr);
		
	}else if(pid == ALL_PROC){
		while(*event!=NULL) {
			// remove the first waiting proc of the waiting list:
			proc_ptr = *event;
			*event = proc_ptr->next;
			proc_ptr->next = NULL;
			//
			proc_ptr->status = TASK_RUNNING;
			enqueue(proc_ptr);
		}
	}else{
		p = event;
		while((*p!=NULL) &&((*p)->pid != pid))  p = &((*p)->next);
		if(*p == NULL) return FAILURE;
		// remove the first waiting proc of the waiting list:
		proc_ptr = *p;
		*p = proc_ptr->next;
		proc_ptr->next = NULL;
		//
		proc_ptr->status = TASK_RUNNING;
		enqueue(proc_ptr);	
	}
	return SUCCESS; //success
}
static void timeout()
{
	if(procs.running_proc->priority > 4
		&&  procs.running_proc->priority < NR_PRIORITY-1) {
		dequeue(procs.running_proc);
		if(++procs.running_proc->priority == NR_PRIORITY-1) 
			procs.running_proc->priority = 5;
		enqueue(procs.running_proc);
	}else{
		dequeue(procs.running_proc);
		enqueue(procs.running_proc);
	}
	schedule();
}

long do_clock_interrupt(struct irq_hook * hook)
{
	static int clock = 20;
	// check if the hook is clock's hook:
	if(hook->irq!=IRQ_CLOCK) return FAILURE;

	// increase the jiffies:
	procs.jiffies ++;

	// send EOI to the pic:
	SEND_MASTER_EOI();
	
	if((--clock)==0){
		sys_notify(HARDWARE, CLOCK);
		clock = 20;
	}

	if(procs.running_proc->reenter == 1){
		procs.running_proc->utime ++;
	}else{
		procs.running_proc->ktime ++;
	}
	// current proc's counter --
	procs.running_proc->counter--;
	
	// check if the proc's counter is use up...
	if(procs.running_proc->counter > 0) return SUCCESS;
	
	xycprintk(2,0,TEXT_FR_I|TEXT_FR_G,
			"[%ld]: jiffies = %ld, priority=%d, ktime=%d, utime=%d, reenter=%d", 
			procs.running_proc->pid, procs.jiffies,  procs.running_proc->priority,
		procs.running_proc->ktime, 
		procs.running_proc->utime, procs.running_proc->reenter);
	
	// the proc's time has use up:
	procs.running_proc->counter = 20;
	
	// if the proc is running in the kernel-mode before the clock-interrupt, then don't schedule:
	if(procs.running_proc->reenter != 1) return SUCCESS;//工作在内核态时，直接返回

	timeout();
	
	return SUCCESS;	
}

void sched_init(void)
{
	int i;
	procs.jiffies = 0;
	procs.last_pid = 0;
	procs.last_proc_used_math = 0;
	procs.running_proc = 0;
	for(i=0; i<NR_PRIORITY; i++){
		procs.ready_head[i] = 0;
		procs.ready_tail[i] = 0;
	}
	for(i=0;i<NR_TASKS; i++){
		procs.task[i] = 0;
	}
	set_boot_procs();

	irq_put_hook(IRQ_CLOCK, do_clock_interrupt, 0);
	/* 
	 * 设置时钟中断的产生速率是100Hz/s
	 */
	i8253_init(0,3,LATCH);
	enable_irq_clock();
}
